﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using InstanceController.RolesPerfMonDAL;
using InstanceController.WADPerformanceCountersDAL;
using WADConfigureWebRole;

namespace InstanceControllerWorker.DynaScale
{
	/// <summary>
	/// The evaluation class captured diagnostic data and time interval data and makes
	/// a determination about whether more capacity is needed or if there is excess
	/// capacity. The evaluation is NOT concerned with increment steps minimums or 
	/// maximums, it ONLY gives a direction: up, down or no change.
	/// </summary>
	public class Evaluation
	{
		#region Private
		private string _deploymentId = string.Empty;
		private string _roleName = string.Empty;
		#endregion

		#region Public properties
		/// <summary>
		/// List of CounterSettingsDataModel objects.
		/// </summary>
		public static List<CounterSettingsDataModel> CounterSettingsDataModelList;

		private bool _canRun;
		/// <summary>
		/// Gets or sets a value indicating whether this evaluation instance can run.
		/// </summary>
		/// <value><c>true</c> if this instance can run; otherwise, <c>false</c>.</value>
		public bool CanRun
		{
			get { return _canRun; }
			set { _canRun = value; }
		}
		#endregion

		/// <summary>
		/// Gets or sets the metrics.
		/// </summary>
		/// <value>The metrics.</value>
		protected List<Metric> Metrics { get; set; }

		/// <summary>
		/// Initializes a new instance of the <see cref="Evaluation"/> class.
		/// During instancing the class it is determined if an evaluation is even
		/// possible to run.  The "CanRun" public property is true only if configuration
		/// was successful and the "RunEvaluationConfiguration" method successfully
		/// completes.
		/// </summary>
		/// <param name="configurationSuccess">if set to <c>true</c> [configuration success].</param>
		public Evaluation(bool configurationSuccess)
		{
			_canRun = configurationSuccess;
			if (!configurationSuccess) return;

			ConfigureEvaluationInstance();
		}

		/// <summary>
		/// Configures the evaluation class instance. Attempts to load configuration settings
		/// into the instance.  Assumes that the worker role has been properly configured.
		/// </summary>
		/// <returns></returns>
		public bool ConfigureEvaluationInstance()
		{
			try
			{
				CounterSettingsDataModelList =
					CounterSettingsDataMethods.ReadCounterSettingsDataModelRows(WorkerRole.ConfigurationDataModel.SubscriptionID).ToList();
				_deploymentId = WorkerRole.ConfigurationDataModel.DeploymentID;
				_roleName = WorkerRole.ConfigurationDataModel.RoleName;

				if (CounterSettingsDataModelList.Count > 0 && !String.IsNullOrEmpty(_deploymentId) && !String.IsNullOrEmpty(_roleName))
					_canRun = true;

			}
			catch (Exception)
			{
				_canRun = false;
			}
			return _canRun;
		}

		/// <summary>
		/// Evaluates the system status.  This method is tri-state: -1 decrement, 0 no action, 1 increment.
		/// The return just gives an indicator of capacity requirements: more, less, or no change. The
		/// worker must determine IF it can grant the request (min/max range, stepping are not part of this
		/// return.)
		/// </summary>
		/// <returns></returns>
		public int EvaluateSystemStatus(out Metric counterMetric)
		{
			counterMetric = null;
			if (!_canRun) return 0; //if we can't run return the no action status

			if (WorkerRole.CheckForActiveChangesPending())	//dampens the feedback loop
				return 0;

			int actionIndicator = 0;
			Metrics = GetMetricData(_roleName, _deploymentId);

			var step = 0;
			var indicator = new int[3];
			var value = new double[3];
			var ts = DateTime.Now;
			foreach (var metric in Metrics)
			{
				if (metric.Name == PlugIn.ProcessorCounterName) 
				{
					ts = metric.Timestamp;
					value[step] = metric.CurrentValue;
					if (metric.CurrentValue >= metric.IncThreshold)
					{
						indicator[step] = 1;	// indicates up
					}
					else if (metric.CurrentValue <= metric.DecThreshold)
					{
						indicator[step] = -1;	// indicates down
					}
					else
					{
						indicator[step] = 0;	// no change
					}
					step++;	//increment the indicator array pointer
				}

				// other counter data processing could occur here...
			}

			if (step > 0 && step <=3)	//did anything process?
			{
				step = 1;
				//Trying to find a way to spin up faster in response to demand
				//and to keep them up until demand is clearly falling off
				
				// indexes: 0 = FastAttack, 1 = ShortInterval, 2 = LongInterval
				if (indicator[0] == 1 && indicator[1] == 1)
				{
					actionIndicator = 1; // up
					step = 1;
				}

				if (indicator[0] == 1 && indicator[1] == 0 && indicator[2] == 0)
				{
					actionIndicator = 1; // up
					step = 0;
				}

				if (indicator[0] == 1 && indicator[1] == -1 && indicator[2] == 0)
				{
					actionIndicator = 0; // no action
					step = 1;
				}

				if (indicator[0] <= 0 && indicator[1] == -1 && indicator[2] <= 0)
				{
					actionIndicator = -1; // down
					step = 1;
				}

				if (indicator[0] <= 0 && indicator[1] == 0 && indicator[2] <= 0)
				{
					actionIndicator = 0; // no action
					step = 1;
				}

				if (indicator[0] <= 0 && indicator[1] == 0 && indicator[2] >= 0)
				{
					actionIndicator = 0; // no action
					step = 1;
				}

				if (indicator[1] == 0 && indicator[2] >= 0)
				{
					actionIndicator = 0; // no action
					step = 1;
				}

				Trace.WriteLine(String.Format("Indicators {0}, {1}, {2}", indicator[0], indicator[1], indicator[2]));
				Trace.WriteLine(String.Format("Values {0}, {1}, {2}", value[0], value[1], value[2]));

				counterMetric = new Metric
				                	{
				                		Name = PlugIn.ProcessorFriendlyName,
				                		CurrentValue = value[step],
				                		Tag = _roleName,
				                		Timestamp = ts
				                	};
			}
			return actionIndicator;
		}

		/// <summary>
		/// Gets the metric data. 
		/// First it reads a list of active counters. Then it loops through the counters.
		/// Reads raw data from the Azure diagnostics performance counters for each counter listed.
		/// The read command ultimately happens in the WADPerformanceCountersDataSource class. 
		/// It averages each counter and adds the metric data to the list of Metrics returned.
		/// </summary>
		/// <returns></returns>
		public List<Metric> GetMetricData(string roleName, string deploymentID)
		{
			var metricAverages = new List<Metric>();
			var performanceCounterItems = WorkerRole.GetPerformanceCountersList();	// Gets all the active counters
			foreach (var performanceCounterItem in performanceCounterItems)
			{
				Metric metric;
				if (performanceCounterItem.CounterName == PlugIn.ProcessorCounterName)
				{
					metric = CalculateMetricsFromDiagnostics(performanceCounterItem, WorkerRole.TimeDefinitions.FastAttack, roleName, deploymentID);
					if (metric != null)
						metricAverages.Add(metric);
					metric = CalculateMetricsFromDiagnostics(performanceCounterItem, WorkerRole.TimeDefinitions.ShortInterval, roleName, deploymentID);
					if (metric != null)
						metricAverages.Add(metric);
					metric = CalculateMetricsFromDiagnostics(performanceCounterItem, WorkerRole.TimeDefinitions.LongInterval, roleName, deploymentID);
					if (metric != null)
						metricAverages.Add(metric);
				}
				else
				{
					metric = CalculateMetricsFromDiagnostics(performanceCounterItem, WorkerRole.TimeDefinitions.ShortInterval, roleName, deploymentID);
					if (metric != null)
						metricAverages.Add(metric);
				}
			}
			return metricAverages;
		}

		/// <summary>
		/// Calculates the metrics from diagnostic data.
		/// </summary>
		/// <param name="performanceCounterItem">The performance counter item.</param>
		/// <param name="sampleWindow">The sample window.</param>
		/// <param name="roleName">Name of the role.</param>
		/// <param name="deploymentID">The deployment ID.</param>
		/// <returns></returns>
		private static Metric CalculateMetricsFromDiagnostics(PerformanceCounterItem performanceCounterItem, double sampleWindow, string roleName, string deploymentID)
		{
			List<WADPerformanceCountersDataModel> perfCounterValues = null;
			try
			{
				// Get the Performance Counters from the WAD Performance Counters
				perfCounterValues =
					WADPerformanceCountersDataMethods.RetrievePerformanceCounterValues(
						deploymentID, roleName, DateTime.Now.Subtract(TimeSpan.FromMinutes(1)),
						performanceCounterItem.CounterName, sampleWindow);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(String.Format("Unable to retrieve Performance Counters {0}", ex.Message));
			}

			var decThreshold = performanceCounterItem.DecThreshold;	// prevents "access to modified closure"
			var incThreshold = performanceCounterItem.IncThreshold;	// prevents "access to modified closure"
			if (perfCounterValues != null && perfCounterValues.Count > 0)
			{
				// take the average value for each named performance counter
				//List<Metric> averageMetrics = null;
				var averageMetrics = (from p in perfCounterValues
								  group p by p.CounterName
									  into g
									  select new Metric
									  {
										  Name = g.Key,
										  CurrentValue = Math.Round(g.Average(p => p.CounterValue), 2),
										  Tag = roleName,
										  Timestamp = DateTime.Now,
										  DecThreshold = decThreshold,
										  IncThreshold = incThreshold
									  }).FirstOrDefault();
				return averageMetrics;
			}
			return null;
		}

	}
}